[b5be3a]: / 1 - Methods with Improved Results / SegmentationFunctions.py

Download this file

459 lines (324 with data), 15.8 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
import os
import cv2
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from sklearn.cluster import KMeans
from skimage.morphology import erosion, opening, closing, square, \
disk, convex_hull_image, remove_small_holes
from skimage.measure import label, regionprops
from skimage.filters import sobel
from skimage.feature import canny
from scipy import ndimage as ndi
from skimage.segmentation import watershed
SMALL_FONT = 13
MEDIUM_FONT = 15
LARGE_FONT = 18
plt.rc('font', size=SMALL_FONT) # controls default text sizes
plt.rc('axes', titlesize=SMALL_FONT) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_FONT) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_FONT) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_FONT) # fontsize of the tick labels
plt.rc('legend', fontsize=MEDIUM_FONT) # legend fontsize
plt.rc('figure', titlesize=LARGE_FONT) # fontsize of the figure title
plt.rcParams["figure.figsize"] = (10, 5)
def readSortedSlices(path):
slices = []
for s in os.listdir(path):
slices.append(path + '/' + s)
slices.sort(key = lambda s: int(s[s.find('_') + 1 : s.find('.')]))
ID = slices[0][slices[0].find('/') + 1 : slices[0].find('_')]
print('CT scan of Patient %s consists of %d slices.' % (ID, len(slices)))
return (slices, ID)
def getSliceImages(slices):
return list(map(readImg, slices))
def readImg(path, showOutput=0):
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
if showOutput:
plt.title('A CT Scan Image Slice')
plt.imshow(img, cmap='gray')
return img
def imgKMeans(img, K, showOutput=0, showHistogram=0):
'''
Apply KMeans on an image with the number of clusters K
Input: Image, Number of clusters K
Output: Dictionary of cluster center labels and points, Output segmented image
'''
imgflat = np.reshape(img, img.shape[0] * img.shape[1]).reshape(-1, 1)
kmeans = KMeans(n_clusters=K, verbose=0)
kmmodel = kmeans.fit(imgflat)
labels = kmmodel.labels_
centers = kmmodel.cluster_centers_
center_labels = dict(zip(np.arange(K), centers))
output = np.array([center_labels[label] for label in labels])
output = output.reshape(img.shape[0], img.shape[1]).astype(int)
if showOutput:
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(10, 5))
axes = axes.ravel()
axes[0].imshow(img, cmap='gray')
axes[0].set_title('Original Image')
axes[1].imshow(output)
axes[1].set_title('Image after KMeans (K = ' + str(K) + ')')
return center_labels, output
def preprocessImage(img, showOutput=0):
'''
Preprocess the image by applying truncated thresholding using KMeans
Input: Image
Output: Preprocessed image
'''
centroids, segmented_img = imgKMeans(img, 3, showOutput=0)
sorted_center_values = sorted([i[0] for i in centroids.values()])
threshold = (sorted_center_values[-1] + sorted_center_values[-2]) / 2
retval, procImg = cv2.threshold(img, threshold, 255, cv2.THRESH_TOZERO)
if showOutput:
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(10, 5))
axes = axes.ravel()
axes[0].imshow(img, cmap='gray')
axes[0].set_title('Original Image')
axes[1].imshow(procImg, cmap='gray')
axes[1].set_title('Processed Image - After Thresholding')
return procImg, threshold
def getForegroundMask(img, fg_threshold, showOutput=0):
retval, init_fg_mask = cv2.threshold(img, fg_threshold, 255, cv2.THRESH_BINARY)
# Morphological operations to clean the mask
fg_mask_opened = opening(init_fg_mask, square(3))
fg_mask_opened2 = opening(fg_mask_opened, disk(4))
# Perform edge-based segmentation of the foreground...
# Detect contours that delineate the foreground with the Canny edge detector
edges = canny(fg_mask_opened2) # Background is uniform - edges are on the boundary/inside ROI
# Fill the inner part of the boundary using morphology ops
fg_mask = ndi.binary_fill_holes(fg_mask_opened2)
# Plot all steps
if showOutput:
fig, axes = plt.subplots(2, 3, sharex=True, sharey=True, figsize=(16, 10))
axes = axes.ravel()
axes[0].set_title('Original Image')
axes[0].imshow(img, cmap='gray')
axes[1].set_title('On Performing Thresholding\'s')
axes[1].imshow(init_fg_mask, cmap='gray')
axes[2].set_title('On Opening with Square SE (3)')
axes[2].imshow(fg_mask_opened, cmap='gray')
axes[3].set_title('On Opening with Disk SE (4)')
axes[3].imshow(fg_mask_opened2, cmap='gray')
axes[4].set_title('Outer Boundary Delineation with Canny\'s')
axes[4].imshow(edges, cmap='gray')
axes[5].set_title('Foreground Mask')
axes[5].imshow(fg_mask, cmap='gray')
return fg_mask
def getLungTracheaMasks(img, fg_mask, fg_threshold, showOutput=0):
# Distinguish black pixels of the background from those of the lungs
enhanced = img.copy()
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if fg_mask[i][j] == 0:
enhanced[i][j] = 255
# Extract lungs from the foreground mask
retval, initial_lung_mask = cv2.threshold(enhanced, fg_threshold, 255, cv2.THRESH_BINARY_INV)
# Clean up the lung mask with morphological operations
lung_mask_op = opening(initial_lung_mask, square(2))
lung_mask_opcl = closing(lung_mask_op, disk(6))
lung_mask_opclrm = ndi.binary_fill_holes(lung_mask_opcl)
# Get connected components of the segmented image and label them
label_img = label(lung_mask_opclrm)
lung_regions = regionprops(label_img)
# Upon experimentation: areas of regions < 1500 are wind pipe structures
trachea_labels = []
for i in lung_regions:
if i.area < 1500:
trachea_labels.append(i.label)
# Create trachea mask as a summation of all those regions
trachea_mask = np.zeros(img.shape, dtype=np.uint8)
for row in range(label_img.shape[0]):
for col in range(label_img.shape[1]):
if label_img[row][col] in trachea_labels:
trachea_mask[row][col] = 255
# Lung mask is made of all the other regions
lung_mask = lung_mask_opclrm * np.invert(trachea_mask)
# Lung mask is all black? Convex hull set to 0, since convex hull op on empty img errors out
if sum(sum(lung_mask)) > 0:
ch_lung_mask = convex_hull_image(lung_mask)
else:
ch_lung_mask = lung_mask.copy()
initial_int_heart_mask = ch_lung_mask * np.invert(lung_mask) * np.invert(trachea_mask)
int_heart_mask_op1 = opening(initial_int_heart_mask, square(5))
int_heart_mask_op2 = opening(int_heart_mask_op1, disk(4))
heart_label_img = label(int_heart_mask_op2)
heart_regions = regionprops(heart_label_img)
areas = {}
for i in heart_regions:
areas[i.label] = i.area
if areas:
heart_label = max(areas, key=areas.get)
int_heart_mask = np.where(heart_label_img==heart_label, np.uint8(255), np.uint8(0))
else:
int_heart_mask = np.zeros(img.shape, dtype=np.uint8)
if showOutput:
fig, axes = plt.subplots(4, 3, sharex=True, sharey=True, figsize=(20, 20))
axes = axes.ravel()
axes[0].set_title('Original Image')
axes[0].imshow(img, cmap='gray')
axes[1].set_title('Enhanced Image')
axes[1].imshow(enhanced, cmap='gray')
axes[2].set_title('Initial Lung Mask')
axes[2].imshow(initial_lung_mask, cmap='gray')
axes[3].set_title('On Opening with Square SE (2)')
axes[3].imshow(lung_mask_op, cmap='gray')
axes[4].set_title('On Closing with Disk SE (6)')
axes[4].imshow(lung_mask_opcl, cmap='gray')
axes[5].set_title('On Filling Regions')
axes[5].imshow(lung_mask_opclrm, cmap='gray')
axes[6].set_title('Trachea Mask/Primary Bronchi')
axes[6].imshow(trachea_mask, cmap='gray')
axes[7].set_title('Lung Mask')
axes[7].imshow(lung_mask, cmap='gray')
axes[8].set_title('Convex Hull of Lung Mask')
axes[8].imshow(ch_lung_mask, cmap='gray')
axes[9].set_title('Initial Intermediate Heart Mask')
axes[9].imshow(initial_int_heart_mask, cmap='gray')
axes[10].set_title('On Opening with Square SE (3)')
axes[10].imshow(int_heart_mask_op1, cmap='gray')
axes[11].set_title('Intermediate heart mask')
axes[11].imshow(int_heart_mask, cmap='gray')
return trachea_mask, lung_mask, ch_lung_mask, int_heart_mask
def chullSpineMask(img, int_heart_mask, showOutput=0):
# If no heart
if not int_heart_mask.any():
return int_heart_mask, int_heart_mask
int_heart_pixel = img.copy()
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if int_heart_mask[i][j] == 0:
int_heart_pixel[i][j] = 0
centroids, segmented_heart_img = imgKMeans(int_heart_pixel, 3, showOutput=0)
spine_threshold = (max(centroids.values()))[0]
retval, initial_spine_mask = cv2.threshold(int_heart_pixel, spine_threshold, 255, cv2.THRESH_BINARY)
bone_mask = closing(initial_spine_mask, disk(20))
label_spine = label(bone_mask)
spine_regions = regionprops(label_spine)
# Assumption: The spine's area is greater than that of any calcium deposits
labels = []
areas = {}
geometric_measures = {}
for i in spine_regions:
labels.append(i.label)
areas[i.label] = i.area
geometric_measures[i.label] = [i.centroid, i.orientation, i.axis_major_length]
spine_label = max(areas, key=areas.get)
labels.remove(spine_label)
spine_mask = np.where(label_spine==spine_label, np.uint8(255), np.uint8(0))
# if labels:
# calcium_deposit_mask = np.where(label_spine==labels[0], np.uint8(255), np.uint8(0))
# else:
# calcium_deposit_mask = np.zeros(img.shape, dtype=np.uint8)
label_heart = label(int_heart_mask)
heart_regions = regionprops(label_heart)
heart_region_area = heart_regions[0].area
spine_region_area = areas[spine_label]
frac_heart = (heart_region_area - spine_region_area)/heart_region_area
if frac_heart < 0.5:
heart_mask = np.zeros(img.shape, dtype=np.uint8)
make_spine_mask = 0
else:
make_spine_mask = 1
# Center point of the spine - get the centroid
y0, x0 = geometric_measures[spine_label][0]
orientation = geometric_measures[spine_label][1]
# Top-most point of the spine
# top_most_coordinate = centroid - (slightly_more_than_half * axis_major_length * sin(angle))
x2 = x0 - math.sin(orientation) * 0.6 * geometric_measures[spine_label][2]
y2 = y0 - math.cos(orientation) * 0.6 * geometric_measures[spine_label][2]
chull_spine_mask = spine_mask.copy()
# Vertical axis
for i in range(math.ceil(y2), img.shape[1]):
if i > math.ceil(y0):
# Horizontal axis
for j in range(img.shape[0]):
chull_spine_mask[i][j] = 255
else:
# Horizontal axis
for j in range(math.ceil(x0)):
chull_spine_mask[i][j] = 255
heart_mask = int_heart_mask * np.invert(chull_spine_mask)
if showOutput:
fig, axes = plt.subplots(3, 3, sharex=True, sharey=True, figsize=(15, 15))
axes = axes.ravel()
axes[0].set_title('Intermediate Heart Mask')
axes[0].imshow(int_heart_mask, cmap='gray')
axes[1].set_title('Intermediate Heart Segment')
axes[1].imshow(int_heart_pixel, cmap='gray')
axes[2].set_title('Intermediate Heart Segment on K-Means (K = 3)')
axes[2].imshow(segmented_heart_img)
axes[3].set_title('Spine Mask')
axes[3].imshow(initial_spine_mask, cmap='gray')
axes[4].set_title('On Closing with Disk SE (20)')
axes[4].imshow(spine_mask, cmap='gray')
axes[5].set_title('On Opening with Square SE (4)')
axes[5].imshow(spine_mask, cmap='gray')
axes[6].set_title('Centroid and uppermost point')
axes[6].imshow(spine_mask, cmap='gray')
if make_spine_mask:
axes[6].plot((x0, x2), (y0, y2), '-r', linewidth=1.5)
axes[6].plot(x0, y0, '.g', markersize=5)
axes[6].plot(x2, y2, '.b', markersize=5)
axes[7].set_title('Convex Hull of Spine Mask')
axes[7].imshow(chull_spine_mask, cmap='gray')
else:
axes[7].set_title('Convex Hull of Spine Mask')
axes[7].imshow(chull_spine_mask, cmap='gray')
axes[8].set_title('Heart Mask')
axes[8].imshow(heart_mask, cmap='gray')
return spine_mask, heart_mask
def segmentHeart(img, heart_mask, showOutput=0):
seg_heart = cv2.bitwise_and(img, img, mask=heart_mask)
if showOutput:
plt.figure(figsize=(10, 5))
plt.title('Segmented Heart')
plt.imshow(seg_heart, cmap='gray')
return seg_heart
def segmentHeartLungsTrachea(img, heart_mask, lung_mask, trachea_mask, showOutput=0):
seg_heart = cv2.bitwise_and(img, img, mask=heart_mask)
seg_lungs = cv2.bitwise_and(img, img, mask=lung_mask)
seg_trachea = cv2.bitwise_and(img, img, mask=trachea_mask)
if showOutput:
fig, [ax1, ax2, ax3] = plt.subplots(1, 3, figsize=(12, 6), sharex=True, sharey=False)
ax1.set_title('Segmented Heart')
ax1.imshow(seg_heart, cmap='gray')
ax2.set_title('Segmented Lungs')
ax2.imshow(seg_lungs, cmap='gray')
ax3.set_title('Segmented Trachea')
ax3.imshow(seg_trachea, cmap='gray')
return seg_heart, seg_lungs, seg_trachea
def applyMaskColor(mask, mask_color):
masked = np.concatenate(([mask[ ... , np.newaxis] * color for color in mask_color]), axis=2)
# Matplotlib expects color intensities to range from 0 to 1 if a float
maxValue = np.amax(masked)
minValue = np.amin(masked)
# Therefore, scale the color image accordingly
if maxValue - minValue == 0:
return masked
else:
masked = masked / (maxValue - minValue)
return masked
def getColoredMasks(img, heart_mask, lung_mask, trachea_mask, showOutput=0):
heart_mask_color = np.array([256, 0, 0])
lung_mask_color = np.array([0, 256, 0])
trachea_mask_color = np.array([0, 0, 256])
heart_colored = applyMaskColor(heart_mask, heart_mask_color)
lung_colored = applyMaskColor(lung_mask, lung_mask_color)
trachea_colored = applyMaskColor(trachea_mask, trachea_mask_color)
colored_masks = heart_colored + lung_colored + trachea_colored
if showOutput:
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
ax = axes.ravel()
ax[0].set_title("Original Image")
ax[0].imshow(img, cmap='gray')
ax[1].set_title("Heart Mask")
ax[1].imshow(heart_colored)
ax[2].set_title("Lung Mask")
ax[2].imshow(lung_colored)
ax[3].set_title("Masks")
ax[3].imshow(colored_masks)
return heart_colored, lung_colored, trachea_colored, colored_masks